#include "IOPFSFileDisk.h"
#include "include/PowerLibraryEngine.h"
#include "FileSystem.h"
#ifdef __COMPILE_LINUX__
#include <stdio.h>
#else
#include <io.h>
#endif

#define COPY_FINDDATA_W32(f,c)	f.Name = c.name;		\
	f.Attrib = c.attrib;								\
	f.TimeCreate = (u32)c.time_create;					\
	f.TimeAccess = (u32)c.time_access;					\
	f.TimeWrite = (u32)c.time_write;					\
	f.Size = (u32)c.size;

#define SIGNATURE1	0xab125fc0
#define SIGNATURE2	0x10ede454

CIOPFSFileDisk::CIOPFSFileDisk(stringc dir,stringc filename,CFileSystem *fsp):CIOFile(dir,filename,fsp),FileBuffer(NULL)
{
}

CIOPFSFileDisk::~CIOPFSFileDisk()
{
	DEALLOCATE(Buffer);
}

u32 CIOPFSFileDisk::CopyFromFileToMemory(u32 Size)
{
	if (!fd.IsDirectory && !fd.IsLoaded)
	{
		if (!FileBuffer)
		{
			stringc FileToOpen;
			stringc Temp;
			stringc Temp1;
			bool Result = SeparatePFSFileElements(fd.CompleteName,FileToOpen,Temp,Temp1);
			FileBuffer = (FILE*)FOpenReadBinary(FileToOpen);
		}

		if (FileBuffer)
		{
			pLogDebug << "CIOFilePFSFileDisk::CopyFromFileToMemory: File " << fd.CompleteName << " found" << endl;
			if (FSeek(FileBuffer,fd.PointerToFile))
			{
				pLogDebug << "CIOPFSFileDisk:: Error seeking PFSFile: "<< fd.CompleteName << endl;
				return false;
			}

			if (!Buffer)
				Buffer = new u8[fd.Size];

			u32 Result = FRead(Buffer,Size,(void*)FileBuffer);

			if (Result == Size)
			{
				pLogDebug << "CIOFilePFSFileDisk:: Finished reading file OK\n";
			}
			else
			{
				pLogDebug << "CIOFilePFSFileDisk:: File modified. Using it. Setting new value to" << Result << endl;
				Size = Result;
			}

			FClose((void*)FileBuffer);
			fd.IsLoaded = true;
			FileBuffer = NULL;
			return Result;
		}
		else
		{
			pLogDebug << "CIOFile:: File " << fd.CompleteName << " not found" << endl;
			return 0;
		}
	}

	if (fd.IsLoaded)
		return fd.Size;

	return 0;
}

s32 CIOPFSFileDisk::FRead(void *Buffer,u32 Size,void *BufferFrom)
{
	return fread(Buffer,Size,1,(FILE*)BufferFrom);
}

s32 CIOPFSFileDisk::FClose(void *BufferToClose)
{
	return fclose((FILE*)BufferToClose);
}

s32 CIOPFSFileDisk::FSeek(void *Buffer,u32 PositionToSeek)
{
	return fseek((FILE*)Buffer,PositionToSeek,SEEK_SET);
}

void *CIOPFSFileDisk::FOpenReadBinary(stringc FileToOpen)
{
	return (void*)fopen(FileToOpen.c_str(),"rb");
}

bool CIOPFSFileDisk::OpenFileForWriting(stringc Name)
{
	if (IsDirectoryString(fd.CompleteName))
	{

	}
	else
	{
		FileBuffer = fopen(fd.CompleteName.c_str(),"wb");
	}

	if (FileBuffer)
	{
		fd.Size = 0;
		fd.FileNameOnly = Name;
		fd.IsDirectory = IsDirectoryString(fd.CompleteName);
		fd.IsLoaded = false;
		return true;
	}
	else
	{
		return false;
	}

	return false;
}

IIOFile *CIOPFSFileDisk::CreateBinaryFile(stringc Name)
{
	CIOFile *FileCreated = fs->GetNewCIOFile(fd.CompleteName,Name);
	FileCreated->fd.CompleteName = fd.CompleteName + Name;
	if (!FileCreated->OpenFileForWriting(Name))
	{
		DEALLOCATE(FileCreated);
		return NULL;
	}

	return FileCreated;
}

u32 CIOPFSFileDisk::WriteBinary(void *Buffer,u32 Size)
{
	if (FileBuffer)
		return fwrite(Buffer,Size,1,FileBuffer);
	return 0;
}

bool CIOPFSFileDisk::CloseFile()
{
	bool Ret = (FClose(FileBuffer)!=0);
	FileBuffer = NULL;
	return Ret;
}

bool CIOPFSFileDisk::ReadFileHeader(stringc File)
{
	if (fs->header.Signature1 != SIGNATURE1)
	{
		void *Temp = (FILE*)FOpenReadBinary(File);
		if (Temp)
		{
			FRead((void*)&fs->header,sizeof(PFSHeader),Temp);
			for (u32 i=0;i<fs->header.TotalNodes;i++)
			{
				PFSFilePointer *Newfp = new PFSFilePointer;
				fs->fpvec.push_back(Newfp);
				FRead((void*)Newfp,sizeof(PFSFilePointer),(void*)Temp);
			}
			FClose(Temp);
			return true;
		}
		return false;
	}
	return true;
}

bool CIOPFSFileDisk::ReadFilePointerByIndex(stringc PFSFile,stringc RelativePathInPFS,stringc FileInPFS,u32 Index,FindDataStrc *fdata)
{
	if (ReadFileHeader(PFSFile))
	{
		if (Index<=fs->header.TotalNodes)
		{
			std::vector<stringc> ActualDir;
			std::vector<u32>	FilesRemainingInThisDir;
			ActualDir.push_back("");
			FilesRemainingInThisDir.push_back(0);
			bool Exit=false;
			u32 IndexTotal=0;
			u32 IndexForRecorring=0;
			while (!Exit)
			{
				if (RelativePathInPFS==ActualDir[ActualDir.size()-1])
				{
					if (IndexForRecorring==Index)
					{
						fdata->Name = fs->fpvec[IndexTotal]->FileName;
						fdata->Attrib = fs->fpvec[IndexTotal]->IsDir?1:0;
						fdata->Size = fs->fpvec[IndexTotal]->FileLen;
						fdata->PointerToFile = fs->fpvec[IndexTotal]->FilePointer;
						return true;
					}
					IndexForRecorring++;
				}

				if (fs->fpvec[IndexTotal]->IsDir)
				{
					FilesRemainingInThisDir.push_back(fs->fpvec[IndexTotal]->IsDir - 1);
					stringc Add;
					if (ActualDir[ActualDir.size()-1]=="")
						Add = "";
					else
						Add = "\\";
					ActualDir.push_back(ActualDir[ActualDir.size()-1] + Add + fs->fpvec[IndexTotal]->FileName);
				}
				else if (FilesRemainingInThisDir[FilesRemainingInThisDir.size()-1] > 0)
				{
					FilesRemainingInThisDir[FilesRemainingInThisDir.size()-1]--;
				}
				else
				{
					if (ActualDir.size() > 1)
					{
						FilesRemainingInThisDir.pop_back();
						ActualDir.pop_back();
					}
				}

				IndexTotal++;

				if (IndexTotal>=fs->header.TotalNodes)
					Exit = true;
			}
		}
	}
	return false;
}

bool CIOPFSFileDisk::FindFirst(stringc Path,FindDataStrc *fdata)
{
	stringc DirPFS;
	stringc PathInPFS;
	stringc FileOnly;
	bool Result = SeparatePFSFileElements(Path,DirPFS,PathInPFS,FileOnly);
	if (Result)
	{
		if (ReadFilePointerByIndex(DirPFS,PathInPFS,FileOnly,0,fdata))
		{
			FileSearchingStrc *Temp = new FileSearchingStrc;
			Temp->DirPFS = DirPFS;
			Temp->PathInPFS = PathInPFS;
			Temp->FileOnly = FileOnly;
			Temp->Index = 0;
			fs->GetFileSearching().push_back(Temp);
			SearchingID = fs->GetFileSearching().size() - 1;
			return true;
		}
	}

	return false;
}

bool CIOPFSFileDisk::FindNext(FindDataStrc *fdata)
{
	FileSearchingStrc *fsdata = fs->GetFileSearching()[SearchingID];
	fsdata->Index++;
	if (ReadFilePointerByIndex(fsdata->DirPFS,fsdata->PathInPFS,fsdata->FileOnly,fsdata->Index,fdata))
		return false;

	return true;
}

bool CIOPFSFileDisk::FindClose()
{
//	int Ret = _findclose(SearchingID);
	SearchingID=-1;
	return 0;
}

bool CIOPFSFileDisk::IsSubDir(FindDataStrc *fdata)
{
	stringc ResultStr;
	stringc Temp;
	stringc Temp2;
	return SeparatePFSFileElements(fdata->Name,ResultStr,Temp,Temp2) || fdata->Attrib == 1;
}

bool CIOPFSFileDisk::IsSubString(stringc Str,stringc Inside,int &Begin)
{
	if (Str.size()>=Inside.size())
	{
		for (u32 i=0;i<Str.length()-(Inside.size()-1);i++)
		{
			stringc DirTemp = Str.substr(i,Inside.size());
			if (DirTemp == Inside)
			{
				Begin = i;
				return true;
			}
		}
	}
	Begin = Str.size();
	return false;
}

bool CIOPFSFileDisk::SeparatePFSFileElements(stringc Dir,stringc &PFSFileOnDisk,stringc &PFSFilePath,stringc &PFSFileName)
{
	int b;
	if (IsSubString(Dir,PFS_FILEONDISK_EXT,b))
	{
		PFSFileOnDisk = Dir.substr(0,b+4);

		PFSFilePath = Dir.substr(b+4,Dir.length());
		if (PFSFilePath[0] == '\\')
			PFSFilePath = PFSFilePath.substr(1,(PFSFilePath).size());
		int b1000 = PFSFilePath.find_last_of('\\');
		if (b1000 == -1)
			PFSFilePath = "";

		int b2 = PFSFilePath.find_last_of('\\');
		if (b2!=-1)
		{
			PFSFileName = PFSFilePath.substr(b2+1,PFSFilePath.size());
			if (b2>0)
			{
				PFSFilePath = PFSFilePath.substr(0,b2);
			}
			else
			{
				PFSFilePath = "";
			}
		}
		else
		{
			PFSFileName = "*.*";
		}
		return true;
	}

	return false;

	PFSFilePath = "";
	PFSFileOnDisk = "";
	PFSFileName = "";
	return false;
}
